﻿namespace Site2AppWP8.Modules
{
    using System;
    using System.Globalization;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Navigation;
    using System.Windows.Threading;
    using Microsoft.Phone.Controls;
    using Microsoft.Phone.Net.NetworkInformation;
    using Site2AppWP8.Config;

    public class OfflineModule
    {
        private bool networkConnection = true;
        private DispatcherTimer timer;
        private NoNetworkOverlay noNetworkOverlay;
        private WebConfig webConfig;
        private WebBrowser webBrowser;
        private MainPage mainPage;

        public OfflineModule(WebConfig webConfig, WebBrowser webBrowser, MainPage mainPage)
        {
            // Set the current network status
            this.webConfig = webConfig;
            this.webBrowser = webBrowser;
            this.mainPage = mainPage;
            this.noNetworkOverlay = new NoNetworkOverlay(mainPage);
            this.webBrowser.LoadCompleted += this.Browser_LoadCompleted;
            this.webBrowser.NavigationFailed += this.Browser_NavigationFailed;
        }

        public bool VerifyNetworkConnected()
        {
            if (!this.IsNetworkConnected())
            {
                this.HandleNetworkNotWorking(true);

                // Normally we'd rely on the DeviceNetworkInformation.IsNetworkAvailable event to accomplish this
                // but there appears to be a bug that results in the change notifications not always firing in a timely manor
                // so this fix (using a timer) ensures we know when the status changed within the 2 sec window we are setting up.

                // Setup a timer to check the network status if we haven't set it up before                
                if (this.timer == null)
                {
                    this.timer = new DispatcherTimer();
                    this.timer.Interval = new TimeSpan(0, 0, 2);
                    this.timer.Tick += this.Timer_Tick;
                }

                this.timer.Start();
            }
            else
            {
                // Ensure we haven't gotten mixed up
                this.networkConnection = true;
            }

            return this.networkConnection;
        }

        private async void Browser_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            if (e.Uri != null && e.Uri.Scheme != "javascript" && RedirectModule.IsNotAnAppliedRedirectRule(this.webConfig, e.Uri))
            {
                var internetConnectionIsWorking = await this.InternetConnectionIsWorking();

                if (!internetConnectionIsWorking)
                {
                    this.HandleNetworkNotWorking(true);
                }
                else
                {
                    // Remove the progress bar if we need to show an error message in the browser
                    this.mainPage.HideOverlay();

                    MessageBox.Show("An error has occured while loading new content. This could be due to network connectivity or server issue. Please check your network settings and try again.");
                }
            }
        }

        private void Browser_LoadCompleted(object sender, NavigationEventArgs e)
        {
            // Remove the progress bar
            this.mainPage.HideOverlay();
            this.RestoreFromNetworkNotWorking();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            if (this.IsNetworkConnected())
            {
                // if we were already connected then do nothing
                if (this.networkConnection)
                {
                    return;
                }

                // We weren't connected, but now we are, so we need to hide the "no network" overlay
                this.RestoreFromNetworkNotWorking();
            }
            else
            {
                // If we alread were disconnected then do nothing
                if (!this.networkConnection)
                {
                    return;
                }

                // We were connected, but now we aren't, so we need to show the "no network" overlay
                this.HandleNetworkNotWorking(true);

                this.networkConnection = false;
            }

            this.timer.Stop();
        }

        private async Task<bool> InternetConnectionIsWorking()
        {
            var client = new HttpClient();
            var pingUri = new UriBuilder(this.webConfig.GetBaseUrl());
            var randomNumber = (new Random()).Next(1000000);

            if (string.IsNullOrEmpty(pingUri.Query))
            {
                pingUri.Query = string.Format(CultureInfo.InvariantCulture, "?ping={0}", randomNumber);
            }
            else
            {
                pingUri.Query = string.Format(CultureInfo.InvariantCulture, "{0}&ping={1}", pingUri.Query, randomNumber);
            }

            var response = await client.GetAsync(pingUri.Uri, HttpCompletionOption.ResponseHeadersRead);

            return response.StatusCode != HttpStatusCode.NotFound;
        }

        private bool IsNetworkConnected()
        {
            return DeviceNetworkInformation.IsNetworkAvailable;
        }

        private async void DeviceNetworkInformation_NetworkAvailabilityChanged(object sender, NetworkNotificationEventArgs e)
        {
            if (e.NetworkInterface.InterfaceType != NetworkInterfaceType.None)
            {
                var internetConnectionIsWorking = await this.InternetConnectionIsWorking();

                if (!internetConnectionIsWorking)
                {
                    this.HandleNetworkNotWorking(false);
                }
                else if (!this.networkConnection)
                {
                    this.RestoreFromNetworkNotWorking();
                }
            }
            else
            {
                this.HandleNetworkNotWorking(false);
            }
        }

        private void RestoreFromNetworkNotWorking()
        {
            this.mainPage.Dispatcher.BeginInvoke(() =>
            {
                if (!this.networkConnection)
                {
                    this.networkConnection = true;
                    DeviceNetworkInformation.NetworkAvailabilityChanged -= this.DeviceNetworkInformation_NetworkAvailabilityChanged;

                    this.mainPage.HideOverlay();

                    if (this.webBrowser.CanGoBack)
                    {
                        this.webBrowser.GoBack();
                    }
                    else
                    {
                        this.webBrowser.Navigate(this.webConfig.GetBaseUrl());
                    }
                }
            });
        }

        private void HandleNetworkNotWorking(bool addNetworkAvailabilityChangedHandler)
        {
            this.mainPage.Dispatcher.BeginInvoke(() =>
            {
                if (this.networkConnection)
                {
                    this.networkConnection = false;

                    if (addNetworkAvailabilityChangedHandler)
                    {
                        DeviceNetworkInformation.NetworkAvailabilityChanged += this.DeviceNetworkInformation_NetworkAvailabilityChanged;
                    }

                    this.mainPage.ShowOverlay(this.noNetworkOverlay);
                }
            });
        }
    }
}
